﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace OCRTheNewHope
{
    class Forest
    {
        public Random rnd = new Random();
        public Digit[] Inputs { get; private set; }
        
        public int NumTree { get; private set; }
        public int NumImagesEachTree { get; private set; }
        
        DecisionTree[] Trees;

        public int TotalTrainDigits { get; private set; }
        public int TotalTestDigits { get; private set; }

        public int TrainError { get; private set; }
        public double TrainAccuracy { get; private set; }

        public int TestError { get; private set; }
        public double TestAccuracy { get; private set; }
        
        public Forest(string trainPath, string testPath, int numTree, int numImagesPerTree)
        {
            this.NumTree = numTree;
            this.NumImagesEachTree = numImagesPerTree;
            this.Trees = new DecisionTree[this.NumTree];

            Digit[] testDigits = FileOperations.ReadDatabase(testPath);

            for (int i = 0; i < this.NumTree; i++)
            {
                Digit[] trainDigits = FileOperations.ReadDatabaseRandomly(trainPath, numImagesPerTree);
                this.Trees[i] = new DecisionTree(this, trainDigits, testDigits, i);
                trainDigits = null;
                Console.WriteLine("Tree " + (i + 1) + "/" + this.NumTree + " is trained.");
            }

            Console.WriteLine("Training completed. Testing started...");

            this.TotalTestDigits = testDigits.Length;
            
            this.TestError = this.Test(testDigits);
            this.TestAccuracy = ((double)(this.TotalTestDigits - this.TestError) / this.TotalTestDigits) * 100;
            
            Console.WriteLine("Log file is created to: " + this.Log());
        }

        public byte ComputeWithHistogram(Digit digit)
        {
            double[] accumulativeHistogram = new double[Parameters.HistogramLength];

            for (int i = 0; i < this.Trees.Length; i++)
            {
                double[] temp = this.Trees[i].ComputeWithHistogram(digit);

                accumulativeHistogram.Add(temp);
            }

            int majority = accumulativeHistogram.MaxIndex();

            return (byte)majority;
        }

        public double[] ComputeHistogram(Digit digit)
        {
            double[] accumulativeHistogram = new double[Parameters.HistogramLength];

            for (int i = 0; i < this.Trees.Length; i++)
            {
                double[] temp = this.Trees[i].ComputeWithHistogram(digit);

                accumulativeHistogram.Add(temp);
            }

            return accumulativeHistogram;
        }

        public int Test(Digit[] testDigits)
        {
            int numTrue = 0;
            int numFalse = 0;

            for (int i = 0; i < testDigits.Length; i++)
            {
                byte temp = this.ComputeWithHistogram(testDigits[i]);

                if (temp == testDigits[i].DigitValue)
                    numTrue++;
                else
                    numFalse++;
            }

            return numFalse;
        }

        public string Log()
        {
            string path = FileOperations.GetPath();

            using (StreamWriter writer = File.CreateText(path))
            {
                writer.WriteLine("Date: " + DateTime.Now.ToString());
                writer.WriteLine("Forest total train digits: " + this.TotalTrainDigits);
                writer.WriteLine("Forest total test digits: " + this.TotalTestDigits);
                writer.WriteLine("Forest number of trees: " + this.NumTree);
                writer.WriteLine("Forest number of images each tree: " + this.NumImagesEachTree);
                writer.WriteLine("Forest train error : " + this.TrainError + "/" + this.TotalTrainDigits + " ( " + this.TrainAccuracy + "% Accuracy )");
                writer.WriteLine("Forest test error  : " + this.TestError + "/" + this.TotalTestDigits + " ( " + this.TestAccuracy + "% Accuracy )");
                writer.WriteLine();

                writer.WriteLine(Parameters.Log());

                for (int i = 0; i < this.NumTree; i++)
                {
                    writer.WriteLine(this.Trees[i].LogString);
                    writer.WriteLine();
                }

                writer.WriteLine();
            }

            return path;
        }
    }
}
